﻿using System;

namespace LeRenard.Core.Numbers
{
    /// <summary>
    /// Defines an <see cref="Int32"/> <see cref="IRangeCycler"/> that spits our random numbers between a given <see cref="IRange"/>.
    /// </summary>
    public sealed class RandomInt32RangeCycler : IRangeCycler<int>
    {
        #region Construction
        /// <summary>
        /// Creates a new <see cref="Int32RangeCycler"/> object.
        /// </summary>
        /// <param name="range">The <see cref="IRange"/> of <see cref="Int32"/> the <see cref="ICounter"/> operates in.</param>
        public RandomInt32RangeCycler(IRange<int> range)
            : this(range, (r) => 
            {                 
                if (_random == null)
                    _random = new Random();

                return _random.Next(r.Minimum, r.Maximum);                
            })
        {
        }

        /// <summary>
        /// Creates a new <see cref="Int32RangeCycler"/> object.
        /// </summary>
        /// <param name="range">The <see cref="IRange"/> of <see cref="Int32"/> the <see cref="ICounter"/> operates in.</param>
        /// <param name="randomAlgorithm">A function that takes in an <see cref="IRange"/> that returns a random number.</param>                
        public RandomInt32RangeCycler(IRange<int> range, Func<IRange<int>, int> randomAlgorithm)
        {
            ThrowIf.Null(() => range);
            ThrowIf.Null(() => randomAlgorithm);

            _range = range;
            _randomAlgorithm = randomAlgorithm;

            Reset();
        }
        #endregion

        #region IRangeCycler
        /// <summary>
        /// Stores a static random object that can be accessed for the default behaviour.
        /// </summary>
        internal static Random _random;

        /// <summary>
        /// Stores the random algorithm.
        /// </summary>
        private readonly Func<IRange<int>, int> _randomAlgorithm;        

        /// <summary>
        /// Gets the <see cref="IRange"/> of <typeparamref name="T"/> this counter operates in.
        /// </summary>
        public IRange<int> Range { get { return _range; } }
        private readonly IRange<int> _range;        

        /// <summary>
        /// Gets the current value in the counter.
        /// </summary>
        public int Current { get { return _currentValue; } }
        private int _currentValue;

        /// <summary>
        /// Stores the next value.
        /// </summary>
        private int _nextValue;

        /// <summary>
        /// Gets the next value in the counter.
        /// </summary>
        /// <returns>The next <typeparamref name="T"/> value.</returns>
        public int Next()
        {
            var nextValue = _nextValue;
            _currentValue = nextValue;

            _nextValue = _randomAlgorithm(this.Range);            

            return nextValue;
        }

        /// <summary>
        /// Peeks what the next value in the counter will be.
        /// </summary>
        /// <returns>The next <typeparamref name="T"/> value.</returns>
        public int Peek()
        {
            return _nextValue;
        }

        /// <summary>
        /// Resets the <see cref="ICounter"/>.
        /// </summary>
        public void Reset()
        {
            Reset(Next());
        }

        /// <summary>
        /// Resets the <see cref="ICounter"/> with the given <paramref name="initialValue"/>.
        /// </summary>
        /// <param name="initialValue">The value to resert the <see cref="ICounter"/> to.</param>
        public void Reset(int initialValue)
        {
            if (this.Range.IsInRange(initialValue)) {
                _currentValue = initialValue;
            } else {
                Reset();
            }
        }
        #endregion
    }
}
